package com.lipaluma.context.mapping.impl;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import com.lipaluma.annotations.mapping.MapColumn;
import com.lipaluma.annotations.mapping.MapMultipleColumns;
import com.lipaluma.annotations.merge.OneToMany;
import com.lipaluma.context.mapping.AnnotationMappingManager;
import com.lipaluma.context.mapping.BasicMappingManager;
import com.lipaluma.context.mapping.XmlMappingManager;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.field.mapper.AutomaticFieldMapper;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.mapper.factory.FieldMapperFactory;
import com.lipaluma.field.mapper.factory.MultipleFieldMapperFactory;
import com.lipaluma.field.mapper.mapping.Column;
import com.lipaluma.field.mapper.mapping.Column.Builder;
import com.lipaluma.field.mapper.mapping.MappingOptions;
import com.lipaluma.field.mapper.single.impl.OneToManyFieldMapper;
import com.lipaluma.util.ReflectionUtils;
import com.lipaluma.xml.mapping.XmlColumnMapping;
import com.lipaluma.xml.mapping.XmlFieldMapping;
import com.lipaluma.xml.mapping.XmlMultipleColumnsMapping;

public class ColumnMappingManager implements AnnotationMappingManager, XmlMappingManager, BasicMappingManager {

//	@Override
//	public boolean isMultipleMapping(Field field) {
//		return field.isAnnotationPresent(MapMultipleColumns.class);
//	}
	
	@Override
	public Column initMapping(Field field) {
		Column mapping;
		if(field.isAnnotationPresent(MapColumn.class)) {
			MapColumn annotation = field.getAnnotation(MapColumn.class);
			Builder builder = Column.builder();
			if(annotation.index() > 0)
				builder.withColumnIndex(annotation.index() - 1);
			builder.withColumnName(field.getName());
			if(!annotation.columnName().trim().isEmpty())
				builder.withColumnName(annotation.columnName().trim());
			mapping = builder.build();
		} else {
			mapping = Column.builder().withColumnName(field.getName()).build();
		}
		return mapping;
	}

	@Override
	public List<Column> initMultipleMappings(Field field) {
		if(!field.isAnnotationPresent(MapMultipleColumns.class))
			throw new CsvParserException("Error on field '"+field.getName()+"' : The field mapper choosen needs the presence of the annotation MapMultipleColumns on the field");
		MapMultipleColumns annotation = field.getAnnotation(MapMultipleColumns.class);
		List<Column> columns = new ArrayList<Column>();
		if(annotation.indexes().length > 0) {
			for (int i=0; i<annotation.indexes().length; i++) {
				int index = annotation.indexes()[i];
				Column column = new Column(index);
				StringBuilder headerName = new StringBuilder(field.getName());
				if(annotation.indexes().length > 1)
					headerName.append("_").append(i);
				column.setHeaderName(headerName.toString());
				columns.add(column);
			}
		}
		else if(annotation.names().length > 0) {
			for (String name : annotation.names()) {
				columns.add(new Column(name));
			}
		}
		return columns;
	}

	@Override
	public MappingOptions initOptions(Field field) {
		MappingOptions.Builder builder = MappingOptions.builder();
		if(field.isAnnotationPresent(MapMultipleColumns.class)) {
			MapMultipleColumns annotation = field.getAnnotation(MapMultipleColumns.class);
			builder.isRequired(annotation.required());
			builder.isTrimed(annotation.trim());
			builder.withPattern(annotation.pattern());
			builder.withLocale(annotation.locale());
			builder.withSeparator(annotation.separator());
			builder.isAllkeeped(annotation.keepAll());
			builder.isEachValueRequired(annotation.eachValueRequired());
			return builder.build();
		} else if(field.isAnnotationPresent(MapColumn.class)) {
			MapColumn annotation = field.getAnnotation(MapColumn.class);
			builder.isRequired(annotation.required());
			builder.isTrimed(annotation.trim());
			builder.withPattern(annotation.pattern());
			builder.withLocale(annotation.locale());
			builder.withSeparator(annotation.separator());
			builder.isAllkeeped(annotation.keepAll());
			builder.withTrueValues(annotation.trueValues());
			builder.withFalseValues(annotation.falseValues());
			return builder.build();
		}
		return null;
	}

	@Override
	public Column initMapping(Field field, XmlFieldMapping xmlMapping) {
		XmlColumnMapping xmlColumn = (XmlColumnMapping) xmlMapping;
		Column.Builder builder = Column.builder();
		if(xmlColumn.getIndex() != null)
			builder.withColumnIndex(xmlColumn.getIndex() - 1);
		if(StringUtils.isNotBlank(xmlColumn.getName()))
			builder.withColumnName(xmlColumn.getName());
		else
			builder.withColumnName(field.getName());
		return builder.build();
	}

	@Override
	public List<Column> initMultipleMappings(Field field, XmlFieldMapping xmlMapping) {
		if(!xmlMapping.isMultiple())
			throw new CsvParserException("Error on field '"+field.getName()+"' : the field mapper needs a multiple mapping");
		XmlMultipleColumnsMapping mapping = (XmlMultipleColumnsMapping)xmlMapping;
		List<Column> columns = new ArrayList<Column>();
		if(mapping.getIndexes().length > 0) {
			for (int i = 0; i < mapping.getIndexes().length; i++) {
				int index = mapping.getIndexes()[i];
				Column column = new Column(index - 1);
				column.setHeaderName(field.getName()+"_"+i);
				columns.add(column);
			}
			return columns;
		} else if(mapping.getColumnNames().length > 0) {
			for (String name : mapping.getColumnNames()) {
				if(StringUtils.isBlank(name))
					throw new CsvParserException("Error on field '"+field.getName()+"' : A column name is empty");
				columns.add(new Column(name.trim()));
			}
			return columns;
		}
		else throw new CsvParserException("Error on field multiple '"+field.getName()+"' : Neither indexes or columns names were found");
	}
	
	@Override
	public boolean checkRequiredOnMapping(Field field) {
		if(field.isAnnotationPresent(MapMultipleColumns.class)) {
			MapMultipleColumns annotation = field.getAnnotation(MapMultipleColumns.class);
			return annotation.required();
		} else if(field.isAnnotationPresent(MapColumn.class)) {
			MapColumn annotation = field.getAnnotation(MapColumn.class);
			return annotation.required();
		}
		return false;
	}
	
	@Override
	public boolean checkTrimedOnMapping(Field field) {
		if(field.isAnnotationPresent(MapMultipleColumns.class)) {
			MapMultipleColumns annotation = field.getAnnotation(MapMultipleColumns.class);
			return annotation.trim();
		} else if(field.isAnnotationPresent(MapColumn.class)) {
			MapColumn annotation = field.getAnnotation(MapColumn.class);
			return annotation.trim();
		}
		return false;
	}	

	/**
	 * create a MapColumn adapted for the field
	 * @param field the field
	 * @return a column adapted for the field
	 */
	@Override
	public FieldMapper createFieldMapper(Field field) {
		Class<? extends FieldMapper> fieldMapperClass = findFieldMapperClass(field);
		FieldMapper fieldMapper = FieldMapperFactory.instantiateFieldMapper(fieldMapperClass);			
		if(isOneToMany(field))
			return new OneToManyFieldMapper(fieldMapper);
		return fieldMapper;
	}

	@Override
	public FieldMapper createFieldMapper(Field field, XmlFieldMapping mapping) {
		Class<? extends FieldMapper> fieldMapperClass = findFieldMapperClass(field, mapping);
		FieldMapper fieldMapper = FieldMapperFactory.instantiateFieldMapper(fieldMapperClass);
		if(mapping.isOneToMany())
			return new OneToManyFieldMapper(fieldMapper);
		return fieldMapper;
	}

	@Override
	public Class<?> getTargetClass(Field field) {
		Class<?> targetClass;
		if(isOneToMany(field))
			targetClass = ReflectionUtils.getParameterizedClass(field);
		else
			targetClass = field.getType();
		return targetClass;
	}

	@Override
	public Class<?> getTargetClass(Field field, XmlFieldMapping mapping) {
		Class<?> targetClass;
		if(mapping.isOneToMany())
			targetClass = ReflectionUtils.getParameterizedClass(field);
		else
			targetClass = field.getType();
		return targetClass;
	}

	public boolean isOneToMany(Field field) {
		return field.isAnnotationPresent(OneToMany.class);
	}


	public Class<? extends FieldMapper> findFieldMapperClass(Field field) {
		Class<?> targetClass = getTargetClass(field);
		if(isOneToMany(field)) {
			OneToMany oneToMany = field.getAnnotation(OneToMany.class);
			if(AutomaticFieldMapper.class.equals(oneToMany.with()))
				return FieldMapperFactory.retrieveFieldMapperClassForType(targetClass);
			else 
				return oneToMany.with();
		}if(field.isAnnotationPresent(MapColumn.class)) {
			if(AutomaticFieldMapper.class.equals(field.getAnnotation(MapColumn.class).with()))
				return FieldMapperFactory.retrieveFieldMapperClassForType(targetClass);
			else 
				return field.getAnnotation(MapColumn.class).with();
		}
		else if(field.isAnnotationPresent(MapMultipleColumns.class))
			if(AutomaticFieldMapper.class.equals(field.getAnnotation(MapMultipleColumns.class).with()))
				return MultipleFieldMapperFactory.retrieveMultipleFieldMapperClassForType(targetClass);
			else
				return field.getAnnotation(MapMultipleColumns.class).with();
		else
			return FieldMapperFactory.retrieveFieldMapperClassForType(targetClass);
	}	

	public Class<? extends FieldMapper> findFieldMapperClass(Field field, XmlFieldMapping mapping) {
		Class<?> targetClass = getTargetClass(field, mapping);		
		if(mapping.isMultiple()) {
			XmlMultipleColumnsMapping multipleMapping = (XmlMultipleColumnsMapping)mapping;
			if(AutomaticFieldMapper.class.equals(multipleMapping.with()))
				return MultipleFieldMapperFactory.retrieveMultipleFieldMapperClassForType(targetClass);
			else
				return multipleMapping.with();
		}
		else {
			if(AutomaticFieldMapper.class.equals(mapping.with()))
				return FieldMapperFactory.retrieveFieldMapperClassForType(targetClass);
			else 
				return mapping.with();
		}
	}
	
}
